package TecMaster;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import TecMaster.GDA;

import TecMaster.Equipamentos.*;

/**
 * Classe Empresa do projeto TecMaster. Responsavel pelas informacoes gerais de
 * equipamentos e clientes relacionados.
 * 
 * @author Flavio Ramalho & Theocrito Moura.
 * 
 * @version 0.1
 * 
 */

public class Empresa<E extends Equipamento> implements Serializable {
	public transient HashMap<Cliente, ArrayList<E>> mapaClientesEquipamentos;
	private String nome = "TecMaster";
	public transient HashMap<Equipamento, Integer> mapaDefeitos;
	public transient ArrayList<E> historicoDeEquipamentos;
	private static final long serialVersionUID = 1L;
	public final String Clientes = "DadosDosClientes.dat";
	private final String Historico = "HistoricoDeEquipamentos.dat";
	private GDA<E> gda;

	/**
	 * Construtor default de empresa.
	 * 
	 * @throws IOException
	 * @throws FileNotFoundException
	 * @throws ClassNotFoundException
	 */
	public Empresa() throws FileNotFoundException, IOException,
			ClassNotFoundException {
		this.gda = new GDA<E>();
		if (!gda.verificaArquivos(Clientes)){
			this.mapaClientesEquipamentos = new HashMap<Cliente, ArrayList<E>>();
			gda.guardaArquivos(this.mapaClientesEquipamentos, Clientes);
		}
		this.mapaClientesEquipamentos = (HashMap<Cliente, ArrayList<E>>) gda.leArquivosClientes();
		this.mapaDefeitos = new HashMap<Equipamento, Integer>();
		if (!gda.verificaArquivos(Historico)){
			this.historicoDeEquipamentos = new ArrayList<E>();
			gda.guardaArquivos(this.historicoDeEquipamentos, Historico);
		}
		this.historicoDeEquipamentos = (ArrayList<E>) gda.leArquivosHistorico();

	}

	/**
	 * Cadastra um cliente e um equipamento na empresa.
	 * 
	 * @param cliente
	 *            o cliente a ser cadastrado.
	 * @param equip
	 *            o equipamento do cliente.
	 * @return true se cadastrado com sucesso.
	 * @throws IOException
	 * @throws FileNotFoundException
	 * @throws IOException
	 *             se o cliente ou equipamento for null.
	 */
	public boolean addClienteEEquipamento(Cliente cliente, E equip)
			throws FileNotFoundException, IOException {
		if (cliente == null)
			throw new IllegalArgumentException("cliente nao pode ser null");
		if (equip == null)
			throw new IllegalArgumentException("equipamento nao pode ser null");
		for (Cliente clienteCadastrado : mapaClientesEquipamentos.keySet())
			if (clienteCadastrado.equals(cliente))
				return false;
		ArrayList<E> equipamentos = new ArrayList<E>();
		equipamentos.add(equip);
		mapaClientesEquipamentos.put(cliente, equipamentos);
		gda.guardaArquivos(mapaClientesEquipamentos, Clientes);
		this.historicoDeEquipamentos.add(equip);
		gda.guardaArquivos(this.historicoDeEquipamentos, Historico);
		return true;
	}

	/**
	 * Adiciona um equipamento em uma lista de equipamentos de um cliente
	 * previamente cadastrado.
	 * 
	 * @param cpfDoCliente
	 *            cpf do cliente cadastrado.
	 * @param equipamento
	 *            equipamento que sera adicionado.
	 * @return true se adicionado com sucesso, false caso cliente nao esteja
	 *         cadastrado.
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	public boolean addEquipamento(String cpfDoCliente, E equipamento)
			throws FileNotFoundException, IOException {
		if (cpfDoCliente == null)
			throw new IllegalArgumentException("Entre com o CPF do cliente!");
		if (cpfDoCliente == "" || cpfDoCliente.length() < 11
				|| cpfDoCliente.length() > 11
				|| !cpfDoCliente.matches("^[0-9]*$"))
			throw new IllegalArgumentException("CPF invalido");
		String cpfFormatado = formataCpf(cpfDoCliente);
		if (equipamento == null)
			throw new IllegalArgumentException("equipamento nao pode ser null");
		for (Cliente clienteCadastrado : mapaClientesEquipamentos.keySet()) {
			if (clienteCadastrado.getCpf().equals(cpfFormatado)) {
				ArrayList<E> equipamentosDoCliente = mapaClientesEquipamentos
						.get(clienteCadastrado);
				equipamentosDoCliente.add(equipamento);
				gda.guardaArquivos(mapaClientesEquipamentos, Clientes);
				this.historicoDeEquipamentos.add(equipamento);
				gda.guardaArquivos(this.historicoDeEquipamentos, Historico);
				return true;
			}
		}
		return false;
	}

	/**
	 * Retorna o mapa de clientes relacionado com seus equipamentos.
	 * 
	 * @return o mapa de clientes relacionado com seus equipamentos.
	 */
	public Map<Cliente, ArrayList<E>> getMapaClientesEquipamentos() {
		return mapaClientesEquipamentos;
	}

	/**
	 * Retorna o numero de clientes cadastrados.
	 * 
	 * @return o numero de clientes cadastrados.
	 */
	public int numeroDeClientes() {
		return getMapaClientesEquipamentos().size();
	}

	/**
	 * Retorna o nome da empresa.
	 * 
	 * @return o nome da empresa.
	 */
	public String getNomeEmpresa() {
		return nome;
	}

	/**
	 * Retorna um arrayList de equipamentos do cliente.
	 * 
	 * @param cliente
	 *            o cliente que deseja consultar seus equipamentos.
	 * @return um arrayList de equipamentos do cliente.
	 */
	public ArrayList<E> getEquipamentosDoCliente(Cliente cliente) {
		return mapaClientesEquipamentos.get(cliente);
	}

	/**
	 * Formata o cpf para o formato XXX.XXX.XXX - XX.
	 * 
	 * @param cpf
	 *            o cpf do cliente.
	 * @return o cpf formatado.
	 */
	private String formataCpf(String cpf) {
		return (cpf.substring(0, 3) + "." + cpf.substring(3, 6) + "."
				+ cpf.substring(6, 9) + "-" + cpf.substring(9, 11));
	}

	/**
	 * Pega um cliente na empresa.
	 * 
	 * @param c
	 *            o cliente a ser pego.
	 * @return o cliente.
	 * @throws Exception
	 *             caso o cliente nao esteja na empresa.
	 */
	public Cliente getCliente(Cliente c) throws Exception {
		if (!mapaClientesEquipamentos.containsKey(c))
			throw new Exception("Cliente nao encontrado!");
		for (Cliente cliente : mapaClientesEquipamentos.keySet())
			if (cliente.equals(c))
				return cliente;
		return null;
	}

	/**
	 * Retorna um cliente cadastrado na empresa a partir de seu cpf.
	 * 
	 * @param cpf
	 *            o cpf do cliente.
	 * @return o cliente.
	 * @throws Exception
	 *             caso nao tenha cliente com o cpf passado na empresa.
	 */
	public Cliente localizaCliente(String cpf) throws Exception {
		Cliente c = new Cliente("a", "b", cpf);
		return getCliente(c);
	}

	/**
	 * Checa se o cliente esta cadastrado na empresa.
	 * 
	 * @param c
	 *            o cliente.
	 * @return true se esta cadastrado, false caso contrario.
	 */
	public boolean checaClienteJaCadastrado(Cliente c) {
		for (Cliente cliente : mapaClientesEquipamentos.keySet())
			if (cliente.equals(c))
				return true;
		return false;
	}

	/**
	 * Pefa um equipamento a partir de seu codigo.
	 * 
	 * @param codigo
	 *            o codigo do equipamento.
	 * @return o equipamento referente ao respectivo codigo.
	 * @throws Exception
	 *             caso nao tenha equipamento com o codigo passado.
	 */
	public E getEquipamento(String codigo) throws Exception {
		for (ArrayList<E> listaEquip : mapaClientesEquipamentos.values())
			for (E equip : listaEquip)
				if (equip.getCodigo().equals(codigo))
					return equip;
		throw new Exception("Equipamento nao encontrado!");
	}

	/**
	 * Retorna o cliente a partir de seu equipamento.
	 * 
	 * @param e
	 *            o equipamento do cliente.
	 * @return o cliente.
	 */
	public Cliente getClientePeloEquipamento(Equipamento e) {
		for (ArrayList<E> listaEquip : mapaClientesEquipamentos.values())
			for (Equipamento equip : listaEquip)
				if (equip.equals(e))
					for (Cliente c : mapaClientesEquipamentos.keySet())
						if (mapaClientesEquipamentos.get(c) == listaEquip)
							return c;
		return null;
	}
	
	/**
	 * Remove o cliente e seus equipamentos.
	 * 
	 * @param c
	 *            o cliente.
	 * @param equip
	 *            o equipamento.
	 * @return true se removido com sucesso, false caso contrario.
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public boolean removeEquipamentoCliente(Cliente c, Equipamento equip)
			throws FileNotFoundException, IOException {
		if (mapaClientesEquipamentos.get(c).contains(equip)) {
			mapaClientesEquipamentos.get(c).remove(equip);
			gda.guardaArquivos(mapaClientesEquipamentos, Clientes);
			return true;
		}
		return false;
	}

	/**
	 * Remove o cliente.
	 * 
	 * @param c
	 *            o cliente a ser removido.
	 * @return true se removido com sucesso, false caso contrario.
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public boolean removeCliente(Cliente c) throws FileNotFoundException,
			IOException {
		if (mapaClientesEquipamentos.containsKey(c)) {
			mapaClientesEquipamentos.remove(c);
			gda.guardaArquivos(mapaClientesEquipamentos, Clientes);
			return true;
		}
		return false;
	}

	/**
	 * Retorna um lista de Equipamentos ordenados pela data de saida a partir de
	 * uma determinada data.
	 * 
	 * @param data
	 *            a data que sera tomada.
	 * @return a lista de euipamentos ordenada pela data.
	 */
	public List<E> ordenacaoPorData(Calendar data) {
		List<E> listaOrdenadaEquip = new ArrayList<E>();
		for (List<E> listaEquip : getMapaClientesEquipamentos().values()) {
			for (E equip : listaEquip) {
				if (equip.getEstado() == Equipamento.EstadoDoEquipamento.CONSERTADO
						&& (equip.getDataDeSaida().get(Calendar.DAY_OF_YEAR) <= data
								.get(Calendar.DAY_OF_YEAR) || equip
								.getDataDeSaida().get(Calendar.YEAR) < data
								.get(Calendar.YEAR)))
					listaOrdenadaEquip.add(equip);
			}
		}
		Collections.sort(listaOrdenadaEquip);
		return listaOrdenadaEquip;
	}

	/**
	 * Retorna um mapa de clientes e lista de Equipamento ordenados por nome.
	 * 
	 * @return um mapa de clientes e lista de Equipamento ordenados por nome.
	 */
	public Map<Cliente, ArrayList<E>> ordenacaoNomeCliente() {
		LinkedHashMap<Cliente, ArrayList<E>> mapaOrdenadoDeClientes = new LinkedHashMap<Cliente, ArrayList<E>>();
		ArrayList<Cliente> listaDeClientesOrdenados = new ArrayList<Cliente>();
		for (Cliente c : mapaClientesEquipamentos.keySet()) {
			listaDeClientesOrdenados.add(c);
		}
		Collections.sort(listaDeClientesOrdenados);
		for (Cliente c : listaDeClientesOrdenados) {
			ArrayList<E> listaOrdenadaEquip = new ArrayList<E>();
			for (E e : mapaClientesEquipamentos.get(c)) {
				if (e.getEstado().equals(Equipamento.EstadoDoEquipamento.NO_TECNICO) || e.getEstado().equals(Equipamento.EstadoDoEquipamento.NA_FILA)) {
					if (mapaOrdenadoDeClientes.containsKey(c))
						mapaOrdenadoDeClientes.get(c).add(e);
					else {
						listaOrdenadaEquip.add(e);
						mapaOrdenadoDeClientes.put(c, listaOrdenadaEquip);
					}
				}
			}
		}
		return mapaOrdenadoDeClientes;
	}

	/**
	 * Retorna uma string ordenada com a listagem dos aparelhos de um tipo
	 * escolhido, pode ser facilmente modficada para retornar outroa tipos se
	 * necessario
	 * 
	 * @param classe
	 *            uma classe de equipamento.
	 * @return String ordenada por tipo
	 */
	public HashMap<Cliente, ArrayList<E>> ordenacaoTipoAparelho(Object classe) {
		ArrayList<E> equipamentos; 
		HashMap<Cliente, ArrayList<E>> ordenacao = new HashMap<Cliente, ArrayList<E>>();
		for (Cliente cliente : getMapaClientesEquipamentos().keySet()) {
			equipamentos = new ArrayList<E>();
			for (E equipCliente : getMapaClientesEquipamentos().get(cliente)) {
				if (classe.equals(equipCliente.getClass())) {
					equipamentos.add(equipCliente);

				}
			}
			ordenacao.put(cliente, equipamentos);
		}
		return ordenacao;
	}

	/**
	 * Retorna uma String contendo os aparelhos que deram mais defeitos, dizendo
	 * marca, defeito e quantas vezes aconteceu
	 * 
	 * @return String com os Defeitos
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public HashMap<Equipamento, Integer> ordenacaoDefeitoApresentado() throws FileNotFoundException, IOException {
		HashMap<Equipamento, Integer> mapaDosMaioresDefeitos ;
		mapaDefeitos = new HashMap<Equipamento, Integer>();
		int nTvNotavel = 0;
		int nDvdNotavel = 0;
		int nAparelhoDeSomNotavel = 0;
		int nVideoGameNotavel = 0;
		int nComputadorNotavel = 0;
		Equipamento TVNotavel = null;
		Equipamento DVDNotavel = null;
		Equipamento AparelhoDeSomNotavel = null;
		Equipamento VideoGameNotavel = null;
		Equipamento ComputadorNotavel = null;
		for (E e : historicoDeEquipamentos) {
	
				mapaDefeitos.put(e, 0);

				for (Equipamento aSerComparado : historicoDeEquipamentos) {
					if (e.comparaAparelhos(aSerComparado)) {
						mapaDefeitos.put(e, mapaDefeitos.get(e) + 1);
					}
				}

			

		}

		for (Equipamento notavel : mapaDefeitos.keySet()) {
			if (notavel.getClass().equals(Tv.class)
					&& mapaDefeitos.get(notavel) > nTvNotavel) {
				nTvNotavel = mapaDefeitos.get(notavel);
				TVNotavel = notavel;
			}
			if (notavel.getClass().equals(Dvd.class)
					&& mapaDefeitos.get(notavel) > nDvdNotavel) {
				nDvdNotavel = mapaDefeitos.get(notavel);
				DVDNotavel = notavel;
			}
			if (notavel.getClass().equals(AparelhoDeSom.class)
					&& mapaDefeitos.get(notavel) > nAparelhoDeSomNotavel) {
				nAparelhoDeSomNotavel = mapaDefeitos.get(notavel);
				AparelhoDeSomNotavel = notavel;
			}
			if (notavel.getClass().equals(Computador.class)
					&& mapaDefeitos.get(notavel) > nComputadorNotavel) {
				nComputadorNotavel = mapaDefeitos.get(notavel);
				ComputadorNotavel  = notavel;
			}
			if (notavel.getClass().equals(VideoGame.class)
					&& mapaDefeitos.get(notavel) > nVideoGameNotavel) {
				nVideoGameNotavel = mapaDefeitos.get(notavel);
				VideoGameNotavel = notavel;
			}
		}
		mapaDosMaioresDefeitos = new HashMap<Equipamento, Integer>();
		mapaDosMaioresDefeitos.put(TVNotavel, mapaDefeitos.get(TVNotavel));
		mapaDosMaioresDefeitos.put(DVDNotavel, mapaDefeitos.get(DVDNotavel));
		mapaDosMaioresDefeitos.put(AparelhoDeSomNotavel, mapaDefeitos.get(AparelhoDeSomNotavel));
		mapaDosMaioresDefeitos.put(ComputadorNotavel, mapaDefeitos.get(ComputadorNotavel));
		mapaDosMaioresDefeitos.put(VideoGameNotavel, mapaDefeitos.get(VideoGameNotavel));
		mapaDosMaioresDefeitos.remove(null);
		return mapaDosMaioresDefeitos;
	}
	
	/**
	 * Ordena os equipamentos por garantia
	 * 
	 * @return String dos equipamentos ordenados por garantia
	 */
	public ArrayList<HashMap<Equipamento, Cliente>> ordencaoGarantia() {
		HashMap<Equipamento, Cliente> taGarantido = new HashMap<Equipamento, Cliente>();
		for (Cliente c : mapaClientesEquipamentos.keySet()) {
			for (Equipamento e : mapaClientesEquipamentos.get(c)) {
				if (e.isGarantia()) {
					taGarantido.put(e, c);
				}
			}
		}
		HashMap<Equipamento, Cliente> naoTaGarantido = new HashMap<Equipamento, Cliente>();
		for (Cliente c : mapaClientesEquipamentos.keySet()) {
			for (Equipamento e : mapaClientesEquipamentos.get(c)) {
				if (!e.isGarantia()) {
					naoTaGarantido.put(e, c);
				}
			}
		}
		ArrayList<HashMap<Equipamento, Cliente>> dadosOrganizados = new ArrayList<HashMap<Equipamento, Cliente>>();
		dadosOrganizados.add(taGarantido);
		dadosOrganizados.add(naoTaGarantido);
		return dadosOrganizados;
	}
}